import React, {
  ForwardedRef,
  forwardRef,
  ReactNode, useCallback,
  useEffect,
  useImperativeHandle, useMemo,
  useRef,
  useState
} from "react";
import { useDebounceFn, useMemoizedFn, useRequest, useWhyDidYouUpdate } from "ahooks";
import InfiniteScroll from "react-infinite-scroll-component";
import { Divider, Empty } from "antd";
import {get} from 'lodash';

function useFixMissingScroll({ hasMore, next, scrollableTarget, list }: {
  hasMore: boolean;
  next: ()=>void;
  scrollableTarget?: string | HTMLElement;
  list: any[];
}) {
  const mainElement = useMemo(() => {
    if(!scrollableTarget){
      return document.body;
    }
    if(typeof scrollableTarget === 'string'){
      return document.getElementById(scrollableTarget);
    }
    return scrollableTarget;
  }, [])

  useEffect(() => {
    const hasScroll = mainElement ? mainElement.scrollHeight > mainElement.clientHeight : false
    if (!hasScroll && hasMore) {
      setTimeout(() => {
        next()
      }, 100)
    }
  }, [hasMore, mainElement, list]);

  return {
    mainElement,
  }
}

function ScrollPageList<Param, Res extends {id: number;[key:string]: any}>({
  service,
  param,
  children,
  height,
  order,
  ready = true,
  pageSize=20,
  getScrollableTarget,
  onListChange,
  onLoadingChange,
  emptyDesc
}:{
  order: Order;
  service: (req: Request<Param>)=>Promise<{list: Res[], total: number}>
  param?: Param;
  children?: (list: Res[], count: number, otherParam: {loading: boolean})=>ReactNode;
  height?: string | number;
  ready?: boolean;
  pageSize?: false | number;
  getScrollableTarget?: ()=>HTMLElement;
  onListChange?: (list: Res[], count: number)=>void;
  onLoadingChange?: (loading: boolean)=>void
  emptyDesc?: string;
}, ref: ForwardedRef<PageListRef<Res>>){
  const [list, setList] = useState<Res[]>([]);

  const [count, setCount] = useState<number>(0);
  
  const [hasMore,setHasMore] = useState<boolean>(true);

  const lastRef = useRef<{
    id: number;
    key: string;
  } | undefined>(undefined);

  useEffect(()=>{
    lastRef.current = undefined;
  },[param, order.orderBy, order.direction])

  // useWhyDidYouUpdate('s', {
  //   param,
  // })

  const {
    runAsync,
    loading,
    // @ts-ignore
  } = useRequest(()=>{
    return service({
      ...param,
      pageSize,
      direction: order.direction,
      orderBy: order.orderBy,
      lastId: lastRef.current?.id.toString(),
      lastKey: lastRef.current?.key,
    } as Request<Param>);
  },{
    manual: true,
    ready,
    onSuccess: (res)=>{
      setHasMore(res.list.length >= 20);
      if(lastRef.current?.id){
        setList(origin=>{
          return origin.concat(res.list);
        })
      }else {
        setCount(res.total);
        setList(res.list);
      }
    },
    loadingDelay: 500,
  })

  const { run: runAsyncDebounce } = useDebounceFn(runAsync,{
    wait: 500
  });

  useEffect(() => {
    runAsyncDebounce();
  }, [param, order.orderBy, order.direction]);

  const scrollableTarget = getScrollableTarget ? getScrollableTarget() : undefined;

  const mutate: PageListRef<Res>['mutate'] = useMemoizedFn((cb)=>{
    setList(origin=>{
      return cb(origin);
    });
  })

  const next = ()=>{
    const lastItem = list[list.length - 1];
    if(lastItem){
      lastRef.current = {
        id: lastItem.id,
        key: get(lastItem, order.orderBy),
      }
      runAsync();
    }
  };

  const {mainElement} = useFixMissingScroll({
    hasMore,
    next,
    scrollableTarget,
    list,
  })

  const scrollToTop: PageListRef<Res>['scrollToTop'] = useMemoizedFn(()=>{
    mainElement?.scrollTo({
      top: 0,
      behavior: 'smooth',
    })
  })

  const refresh: PageListRef<Res>['refresh'] = useMemoizedFn(()=>{
    lastRef.current = undefined;
    runAsync();
  })

  useImperativeHandle<any,PageListRef<Res>>(ref, () => {
    return {
      mutate,
      refresh,
      scrollToTop,
    };
  }, []);

  useEffect(() => {
    onListChange?.(list, count);
  }, [list]);

  useEffect(() => {
    onLoadingChange?.(loading);
  }, [loading]);

  const hasChildren = !!children;

  return <>
    {
      (list.length || !hasChildren) ? <InfiniteScroll
        dataLength={list.length}
        next={next}
        hasMore={hasMore}
        loader={!hasChildren ? <></> : <Divider plain>已经在加油了...</Divider>}
        endMessage={!hasChildren ? <></> : <Divider plain>没有啦～🤐</Divider>}
        scrollableTarget={scrollableTarget as any}
        hasChildren={hasChildren}
      >
        {children?.(list, count, { loading })}
      </InfiniteScroll> : <Empty className="mt-[32px] mb-[64px]" description={emptyDesc} />
    }
  </>
}

export interface Order{
  orderBy: string;
  direction: 'DESC' | 'ASC',
}

export interface PageListRef<Res>{
  mutate: (cb: (list: Res[])=>Res[])=>void;
  refresh: ()=>void;
  scrollToTop: ()=>void;
}

type Request<Param> = ( Param & Order & {
  lastId?: string;
  lastKey?: string;
  pageSize?: number;
})

export default forwardRef(ScrollPageList) as typeof ScrollPageList;
